import uuid

import ujson
from peewee import IntegrityError, DoesNotExist
from fastapi.encoders import jsonable_encoder

from cache.pay_way_cache import PayWayCache
from cache.tenant_cache import TenantCache, TenantUserCache, TenantPayWayCache
from cache.language_cache import LanguageCache
from cache.currency_cache import CurrencyCache
from common.g.g import G_LOGGER, G_TENANT_BLOOMFILTER, G_TENANT_EMAIL_BLOOMFILTER, G_ID_WORKER
from common.model.payment.pay import TotalPayWay
from common.utils.depends.common import Page
from common.v.v import CURRENCY, LANGUAGE, TENANT, TENANT_USER, PAYWAY, DATAORCODE
from common.model.tenant.tenant import Tenant, MiddleTenantLanguage, MiddleTenantCurrency, MiddleTenantPayWay, \
    MiddleTenantMall, TenantConfig
from common.model.mall.mall import Mall
from common.model.user.tenant_user import MiddleTenantUserForTenant, TenantUser
from common.utils.resp import RC, ERROR
from common.pkg.str_id import get_short_uuid, ten_to_any
from enums.pay_enum import PayWayStatusEnum
from schema.req_schema.tenant_req_schema import RegisterTenantModel, UpdateTenantModel
from schema.req_schema.tenant_user_req_schema import TenantUserRegisterModel
from service.base import Service


class TenantConfigService(Service):

    async def get_config(self, tenant_id):
        cache_data = None
        try:
            cache_data = await self.cache.get_cache(f"tenant:{tenant_id}:config")
        except Exception as e:
            G_LOGGER.exception(e)
        if cache_data:
            return True, DATAORCODE(data=ujson.loads(cache_data))
        try:
            tc = await TenantConfig.get_instance(self.mgw, condition={"tenant_id": tenant_id})
        except DoesNotExist:
            return False, DATAORCODE(data=None, code=RC.NOT_EXIST, msg="租户配置异常")
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        try:
            self.cache.set_cache(f"tenant:{tenant_id}:config", ujson.dumps(jsonable_encoder(dict(tc))), ex=24*60*60)
        except Exception as e:
            G_LOGGER.exception(e)
        return True, DATAORCODE(data=tc)

    async def add_config(self, tenant_id, m_secret:str=None, c_secret: str = None, appid: str = None, app_secret: str = None):
        ms = m_secret or uuid.uuid4().hex.upper()
        cs = c_secret or uuid.uuid4().hex.upper()
        aid = appid or get_short_uuid(prefix="A-")
        a_s = app_secret or uuid.uuid4().hex
        return await TenantConfig.add_new_instance(self.mgw, tenant_id=tenant_id, m_secret=ms, c_secret=cs, appid=aid, app_secret=a_s)


class TenantUserService(Service):
    CACHE_CLS = TenantUserCache

    async def _get_user_id_by_email(self, email):
        cache_key = TENANT_USER.USER_DETAIL_BY_EMAIL.format(email=email)
        try:
            cache_data = await self.cache.get_user_by_email(cache_key)
            if cache_data:
                tuser = ujson.loads(cache_data)
                user_id = tuser.get("user_id")
                return user_id
        except Exception as e:
            G_LOGGER.exception(f"缓存获取用户数据失败：{e}")
        try:
            user = await TenantUser.get_instance(self.mgw, condition={"email": email, "removed": False},
                                                 fields=["user_id"])
            user_id = user.user_id
        except DoesNotExist:
            return None
        return user_id

    async def get_user_by_email(self, email):
        try:
            res = await TenantUser.get_instance(self.mgw, condition={"email": email})
            return res
        except DoesNotExist:
            return None

    async def bind_tenant_and_user(self, tenant_id, user_id):
        query = MiddleTenantUserForTenant.insert(user_id=user_id, tenant_id=tenant_id).on_conflict(
            update={"removed": False, "version": MiddleTenantUserForTenant.version + 1})
        update_row = await self.mgw.execute(query)
        if update_row:
            await self.cache.add_user_to_tenant_user_set(tenant_id, user_id)
        return update_row

    async def bind_tenant_and_user_by_email(self, tenant_id, email):
        try:
            user_id = await self._get_user_id_by_email(email)
            if user_id is None:
                return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
            res = await self.bind_tenant_and_user(tenant_id, user_id)
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        return True, DATAORCODE(data={"update_row": res})

    async def remove_user_from_tenant(self, tenant_id, user_id):
        condition = {
            "removed": False,
            "user_id": user_id,
            "tenant_id": tenant_id
        }
        update_fields = {"removed": True, "version": MiddleTenantUserForTenant.version + 1}
        try:
            update_row = await MiddleTenantUserForTenant.update_instance(self.mgw, condition=condition,
                                                                         update_fields=update_fields)
        except Exception as e:
            G_LOGGER.exception(f"更新信息失败:{e}")
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        try:
            if update_row:
                await self.cache.remove_user_from_tenant_user_set(TENANT.TENANT_USER_SET.format(tenant_id=tenant_id))
        except Exception as e:
            G_LOGGER.exception(f"更新缓存信息失败:{e}")
        return True, DATAORCODE(data={"update_row": update_row})

    async def get_users_info(self, tenant_id, page_info: Page):
        # todo 缓存
        # cache_key = f"tenant:user:list:{page_info.page}_{page_info.page_size}"
        # cache_data = await self.cache.get_cache(cache_key)
        # if cache_data:
        #     return
        sub_query = MiddleTenantUserForTenant. \
            select(MiddleTenantUserForTenant.user_id). \
            where(MiddleTenantUserForTenant.tenant_id == tenant_id,
                  MiddleTenantUserForTenant.removed == False)
        query = TenantUser.select(TenantUser.user_id, TenantUser.email, TenantUser.username, TenantUser.removed,
                                  TenantUser.status).where(
            TenantUser.user_id.in_(sub_query.paginate(page_info.page, page_info.page_size)))
        try:
            count = await self.mgw.count(sub_query)
            data = await self.mgw.execute(query.dicts())
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        return True, DATAORCODE(data={"data": list(data), "count": int(count)})

    async def get_tenants_info(self, user_id):
        # todo 缓存
        try:
            sub_query = MiddleTenantUserForTenant.select(MiddleTenantUserForTenant.tenant_id).where(
                MiddleTenantUserForTenant.user_id == user_id)
            query = Tenant.select(Tenant.tenant_id, Tenant.icon, Tenant.name).where(
                Tenant.tenant_id.in_(sub_query)
            )
            res = await self.mgw.execute(query.dicts())
            return True, DATAORCODE(data=list(res))
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")

    async def get_tenant_by_user_and_tenant_id(self, tenant_id, user_id):
        try:
            sub_query = MiddleTenantUserForTenant.select(MiddleTenantUserForTenant.tenant_id).where(
                MiddleTenantUserForTenant.user_id == user_id, MiddleTenantUserForTenant.tenant_id == tenant_id)
            query = Tenant.select(Tenant.tenant_id, Tenant.icon, Tenant.name, Tenant.code, Tenant.slug, Tenant.domain,
                                  Tenant.title, Tenant.owner_id, Tenant.version).where(
                Tenant.tenant_id.in_(sub_query)).dicts()
            res = await self.mgw.get(query)
            return True, res
        except DoesNotExist:
            return False, RC.NOT_EXIST
        except Exception as e:
            G_LOGGER.exception(e)
            return False, ERROR.DB_ERROR

    async def register(self, req_model: TenantUserRegisterModel):
        email = req_model.email
        exist = await G_TENANT_EMAIL_BLOOMFILTER.exist(email)
        if exist:
            res = await self.get_user_by_email(email)
            if res:
                return False, DATAORCODE(data=None, code=RC.PARAM_ERROR, msg="邮箱已被注册")
        data = req_model.dict(exclude_unset=True, exclude_none=True)
        try:
            res = await TenantUser.add_new_instance(self.mgw, **data)
            await G_TENANT_EMAIL_BLOOMFILTER.add(email)
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        return True, DATAORCODE(data={"user_id": res})

    async def update(self, user_id, old_version, **kwargs):
        condition = {
            "user_id": user_id,
            "version": old_version,
            "removed": False
        }
        kwargs["version"] = old_version + 1
        return await TenantUser.update_instance(self.mgw, condition=condition, update_fields=kwargs)

    async def is_user_exist(self, tenant_id, user_id):
        return await self.cache.sismember(TENANT.TENANT_USER_SET.format(tenant_id=tenant_id), user_id)

    async def get_user_info_by_condition(self, **kwargs):
        if "user_id" in kwargs:
            cache_data = await self.cache.get_user_by_id(TENANT_USER.user_detail_by_id.format(user_id=kwargs['user_id']))
            if cache_data:
                return ujson.loads(cache_data)
        try:
            res = await TenantUser.get_instance(self.mgw, condition=kwargs)
        except DoesNotExist:
            return None
        await self.cache.set_cache(TENANT_USER.user_detail_by_id.format(user_id=res.get("user_id")),
                                   ujson.dumps(jsonable_encoder(res)),
                                   ex=30 * 60)
        return res


class TenantLanguageService(Service):

    async def bind_tenant_and_language(self, tenant_id, language_code, is_default=False):
        query = MiddleTenantLanguage.insert(tenant_id=tenant_id, language_code=language_code,
                                            is_default=is_default).on_conflict(
            update={"removed": False, "version": MiddleTenantLanguage.version + 1}
        )
        res_id = await self.mgw.execute(query)
        if is_default is True:
            query = MiddleTenantLanguage.update(is_default=False, version=MiddleTenantLanguage.version + 1).where(
                MiddleTenantLanguage.id != res_id)
            await self.mgw.execute(query)
        return res_id


class TenantCurrencyService(Service):

    async def bind_tenant_and_currency(self, tenant_id, currency_code, is_default=False):
        query = MiddleTenantCurrency.insert(tenant_id=tenant_id, currency_code=currency_code,
                                            is_default=is_default).on_conflict(
            update={"removed": False, "version": MiddleTenantCurrency.version + 1}
        )
        res_id = await self.mgw.execute(query)
        if is_default is True:
            query = MiddleTenantCurrency.update(is_default=False, version=MiddleTenantCurrency.version + 1).where(
                MiddleTenantCurrency.id != res_id)
            await self.mgw.execute(query)
        return res_id

    async def get_default_currency(self, tenant_id):
        condition = {"tenant_id": tenant_id, "removed": False, "is_default": True}
        try:
            res = await MiddleTenantCurrency.get_instance(self.mgw, condition=condition)
        except DoesNotExist:
            return None
        return res


class TenantPayWayService(Service):
    CACHE_CLS = TenantPayWayCache

    async def get_pay_way_list(self, tenant_id):
        # todo 缓存
        try:
            sub_query = MiddleTenantPayWay.select(MiddleTenantPayWay.pay_way_id).where(
                MiddleTenantPayWay.tenant_id == tenant_id)
            query = TotalPayWay.select(TotalPayWay.pay_way_id, TotalPayWay.name, TotalPayWay.code).where(
                TotalPayWay.pay_way_id.in_(sub_query))
            res = await self.mgw.execute(query)
            return True, DATAORCODE(data=res)
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")

    async def add_pay_way(self, tenant_id, pay_way_id):
        try:
            if not await PayWayCache(self.redis).is_pay_way_exist(PAYWAY.TOTAL_PAY_WAY_SET, pay_way_id):
                return False, DATAORCODE(data=None, code=RC.PAYWAY_NOT_EXIST, msg="支付方式不存在")
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        query = MiddleTenantPayWay.insert(tenant_id=tenant_id, pay_way_id=pay_way_id). \
            on_conflict(update={"removed": False,
                                "version": MiddleTenantPayWay.version + 1,
                                "status": PayWayStatusEnum.enable})
        try:
            res = await self.mgw.execute(query)
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        try:
            await self.cache.sadd(TENANT.TENANT_PAYWAY_SET.format(tenant_id=tenant_id), f"payway_{pay_way_id}")
        except Exception as e:
            G_LOGGER.exception(e)
        return True, DATAORCODE(data={"update_row": int(res)})

    async def enable_or_disable_pay_way(self, tenant_id, pay_way_id, enable):
        try:
            exist = await self.cache.sismember(TENANT.TENANT_PAYWAY_SET.format(tenant_id=tenant_id), f"payway_{pay_way_id}")
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.REDIS_ERROR, msg='服务异常')
        if not exist:
            return False, DATAORCODE(data=None, code=RC.FORBIDDING, msg="支付方式不存在")
        try:
            update_row = await MiddleTenantPayWay.update_instance(self.mgw,
                                                                  condition={"tenant_id": tenant_id,
                                                                             "pay_way_id": pay_way_id},
                                                                  update_fields={"status": enable})
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        return True, DATAORCODE(data={"update_row": update_row})

    async def delete_pay_way(self, tenant_id, pay_way_id):
        try:
            exist = await self.cache.sismember(TENANT.TENANT_PAYWAY_SET.format(tenant_id=tenant_id), f"payway_{pay_way_id}")
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.REDIS_ERROR, msg="服务异常")
        if not exist:
            return False, DATAORCODE(data=None, code=RC.FORBIDDING, msg="支付方式不存在")
        try:
            res = await MiddleTenantPayWay.remove_instance(self.mgw,
                                                            condition={"tenant_id": tenant_id, "pay_way_id": pay_way_id})
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        try:
            self.cache.srem(TENANT.TENANT_PAYWAY_SET.format(tenant_id=tenant_id), f"payway_{pay_way_id}")
        except Exception as e:
            G_LOGGER.exception(e)
        return True, DATAORCODE(data={"delete_row": res})


class TenantMallService(Service):

    async def get_mall_info_by_code(self, tenant_id, code):
        cache_data = await self.cache.get_cache(f"tenant:{tenant_id}:mall:{code}")
        if cache_data:
            return ujson.loads(cache_data)
        try:
            res = await MiddleTenantMall.get_instance(self.mgw,
                                                      condition={"tenant_id": tenant_id, "mall_code": code},
                                                      fields=["id", "mall_id"])
        except DoesNotExist:
            return None
        await self.cache.set_cache(f"tenant:{tenant_id}:mall:{code}", ujson.dumps(dict(res)), ex=24 * 60)
        return res

    async def bind_tenant_and_mall(self, tenant_id, mall_code, mall_id):
        res = await MiddleTenantMall.add_new_instance(self.mgw, tenant_id=tenant_id, mall_code=mall_code, mall_id=mall_id)
        await self.cache.set_cache(f"tenant:{tenant_id}:mall:{mall_code}", ujson.dumps({"mall_id": mall_id, "id": res}), ex=24 * 60)
        return res

    async def get_mall_list(self, tenant_id):
        try:
            sub_query = MiddleTenantMall.select(MiddleTenantMall.mall_code).where(MiddleTenantMall.tenant_id == tenant_id,
                                                                                        MiddleTenantMall.removed == False)
            query = Mall.select().where(Mall.code.in_(sub_query))
            res = await self.mgw.execute(query.dicts())

        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")

        return True, DATAORCODE(data=list(res))


class TenantService(Service):
    CACHE_CLS = TenantCache

    async def add_new_tenant(self, user_id, tenant_model: RegisterTenantModel):
        try:
            if tenant_model.invited_code:
                if not await self.cache.is_invite_code_exist(TENANT.TENANT_INVITE_CODE_SET, tenant_model.invited_code):
                    return False, DATAORCODE(data=None, code=RC.INVITE_CODE_NOT_EXIST, msg="邀请码错误")
            if not await CurrencyCache(self.redis).is_currency_exist(CURRENCY.TOTAL_CURRENCY_SET,
                                                                     tenant_model.currency):
                return False, DATAORCODE(data=None, code=RC.CURRENCY_NOT_EXIST, msg="币种不存在")
            if not await LanguageCache(self.redis).is_language_exist(LANGUAGE.TOTAL_LANGUAGE_SET,
                                                                     tenant_model.language):
                return False, DATAORCODE(data=None, code=RC.LANGUAGE_NOT_EXIST, msg="语言不存在")
        except Exception as e:
            G_LOGGER.exception(f"校验失败:{e}")
            return False, DATAORCODE(data=None, code=RC.REDIS_ERROR, msg="服务异常")
        tus = TenantUserService(self.mgw, self.redis)
        tls = TenantLanguageService(self.mgw, self.redis)
        tcs = TenantCurrencyService(self.mgw, self.redis)
        tuc = TenantUserCache(self.redis)
        tcfgs = TenantConfigService(self.mgw, self.redis)
        async with self.mgw.atomic() as f:
            try:
                tenant_data = tenant_model.dict(exclude_none=True)
                tenant_id = await Tenant.add_new_instance(self.mgw, owner_id=user_id, **tenant_data)
                code = f"HH{ten_to_any(tenant_id, 31)}{ten_to_any(G_ID_WORKER.get_id(), 62)}"  # 2+5+9位
                await Tenant.update_instance(self.mgw, condition={"tenant_id": tenant_id}, update_fields={"code": code})
                await tus.bind_tenant_and_user(tenant_id, user_id)
                await tls.bind_tenant_and_language(tenant_id, tenant_model.language, is_default=True)
                await tcs.bind_tenant_and_currency(tenant_id, tenant_model.currency, is_default=True)
                await tcfgs.add_config(tenant_id)
                # code 作为邀请码
                await self.cache.add_invite_code_to_invite_code_set(TENANT.TENANT_INVITE_CODE_SET, code)
                await tuc.add_user_to_tenant_user_set(TENANT.TENANT_USER_SET.format(tenant_id=tenant_id), user_id)
                await G_TENANT_BLOOMFILTER.add(f"tenant:{tenant_id}")
            except IntegrityError as e:
                await f.rollback()
                G_LOGGER.exception(f"重复插入数据:{e}")
                return False, DATAORCODE(data=None, code=RC.SLUG_ALREADY_EXIST, msg="二级域名已经存在")
            except Exception as e:
                await f.rollback()
                G_LOGGER.exception(f"新建租户失败:{e}")
                return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
            return True, DATAORCODE(data={"tenant_id": tenant_id})

    async def update_tenant_info(self, tenant_id, old_version, tenant_model: UpdateTenantModel):
        condition = {
            "tenant_id": tenant_id,
            "removed": False,
            "version": old_version
        }
        update_fields = tenant_model.dict(exclude_unset=True)
        update_fields["version"] = Tenant.version + 1
        try:
            update_row = await Tenant.update_instance(self.mgw, condition=condition, update_fields=update_fields)
        except Exception as e:
            G_LOGGER.exception(f"更新租户信息失败:{e}")
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        try:
            if update_row:
                await self.cache.delete_tenant_detail_info(f"{TENANT.TENANT_DETAIL_INFO.format(tenant_id=tenant_id)}")
        except Exception as e:
            G_LOGGER.exception(f"更新租户缓存失败:{e}")
        return True, DATAORCODE(data={"update_row": update_row})

    async def get_tenant_by_tenant_id(self, tenant_id):
        try:
            cache_data = await self.cache.get_tenant_by_tenant_id(TENANT.TENANT_DETAIL_INFO.format(tenant_id=tenant_id))
            if cache_data:
                return True, DATAORCODE(data=ujson.loads(cache_data))
        except Exception as e:
            G_LOGGER.exception(e)
        try:
            res = await Tenant.get_instance(self.mgw, condition={"tenant_id": tenant_id, "removed": False}, fields=[
                "tenant_id", "create_time", "update_time", "version", "name", "code", "invite_code", "invited_code",
                "status", "slug", "domain", "icon", "title", "owner_id"
            ])
        except DoesNotExist:
            return False, DATAORCODE(data=None, code=RC.NOT_EXIST, msg="租户不存在")
        except Exception as e:
            G_LOGGER.exception(e)
            return False, DATAORCODE(data=None, code=RC.DB_ERROR, msg="服务异常")
        try:
            await self.cache.set_cache(TENANT.TENANT_DETAIL_INFO.format(tenant_id=tenant_id),
                                       ujson.dumps(jsonable_encoder(dict(res))))
        except Exception as e:
            G_LOGGER.exception(e)
        return True, DATAORCODE(data=res)
